package flexibilityDesign;

import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;

import util.Lang;
import util.NullOutputStream;

public class DesignMIP {

	private double lcpm;// c_ij/pi_ij
	private int n;// demand number
	private int m;// resource number
	private int M;// scenario counts

	private double[][] demands;
	private double[] capacities;
	private double[][] c;// link cost
	private double[][] pi;// profit margin

	private int simulation_index = 0;

	public void setSimulationIndex(int index) {
		this.simulation_index = index;
	}

	private boolean writeToFile = true;

	public DesignMIP(int n, int m, int M, double lcpm) {
		this.n = n;
		this.m = m;
		this.M = M;
		this.lcpm = lcpm;
	}

	public void turnOffWriteToFile() {
		this.writeToFile = false;
	}

	private void initData() {
		this.capacities = Support.readCapacities(n, m);
		// double cap = BigDecimal.valueOf(n * 100)
		// .divide(BigDecimal.valueOf(m), 5, BigDecimal.ROUND_HALF_EVEN)
		// .doubleValue();
		// this.capacities = new double[m];
		// for (int j = 0; j < m; j++)
		// this.capacities[j] = cap;
		this.demands = Support.readDemands(M, n, simulation_index);
		this.c = Support.readLinkCost(n, m);
		this.pi = Support.readProfitMargin(n, m);
	}

	public int[][] MIPSolve() {
		this.initData();

		// long timeLimit = 7200000;
		double pro = BigDecimal.ONE.divide(BigDecimal.valueOf(M), 5, BigDecimal.ROUND_HALF_EVEN).doubleValue();

		try {
			long start = System.currentTimeMillis();
			IloCplex master = new IloCplex();

			master.setParam(IloCplex.IntParam.ClockType, 2);
			master.setParam(IloCplex.DoubleParam.TiLim, 3600);
			// master.setOut(new NullOutputStream());
			IloNumVar[][] Y = new IloNumVar[n][m];
			for (int i = 0; i < n; i++)
				for (int j = 0; j < m; j++)
					Y[i][j] = master.boolVar();
			int[][] YValue = new int[n][m];// Y value

			IloNumVar[][][] x = new IloNumVar[M][n][m];
			for (int sm = 0; sm < M; sm++) {
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < m; j++) {
						x[sm][i][j] = master.numVar(0.0, Double.MAX_VALUE);
					}
				}
			}
			IloLinearNumExpr obj = master.linearNumExpr();
			for (int i = 0; i < n; i++)
				for (int j = 0; j < m; j++)
					obj.addTerm(c[i][j], Y[i][j]);
			for (int sm = 0; sm < M; sm++)
				for (int i = 0; i < n; i++)
					for (int j = 0; j < m; j++)
						obj.addTerm(-pi[i][j] * pro, x[sm][i][j]);
			master.addMinimize(obj);

			double[][] initalVal = this.setFlexibilityConfig();
			for (int i = 0; i < n; i++) {
				master.addMIPStart(Y[i], initalVal[i]);
			}

			for (int sm = 0; sm < M; sm++) {
				for (int j = 0; j < m; j++) {
					IloLinearNumExpr ca = master.linearNumExpr();
					for (int i = 0; i < n; i++) {
						ca.addTerm(1.0, x[sm][i][j]);
					}
					master.addLe(ca, capacities[j]);
				}
				for (int i = 0; i < n; i++) {
					IloLinearNumExpr de = master.linearNumExpr();
					for (int j = 0; j < m; j++) {
						de.addTerm(1.0, x[sm][i][j]);
					}
					master.addLe(de, demands[sm][i]);
				}
				for (int i = 0; i < n; i++)
					for (int j = 0; j < m; j++) {
						IloLinearNumExpr cab = master.linearNumExpr();
						cab.addTerm(Y[i][j], capacities[j]);
						master.addLe(x[sm][i][j], cab);
					}
			}

			double[][] init = new double[n][m];
			for (int i = 0; i < n; i++)
				for (int j = 0; j < m; j++)
					init[i][j] = 1.0;
			for (int i = 0; i < n; i++)
				master.addMIPStart(Y[i], init[i]);

			if (master.solve()) {
				StringBuilder builder = new StringBuilder("MIP solve. \t");
				builder.append("n,m,M=" + n + ", " + m + ", " + M + "; index=" + simulation_index + "\r\n");
				double objv = master.getObjValue();
				System.out.println("objvalue=" + objv);
				builder.append("objvalue=" + objv + "\r\n");
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < m; j++) {
						YValue[i][j] = (int) master.getValue(Y[i][j]);
						if (YValue[i][j] == 1) {
							System.out.print("(" + i + "," + j + ")\t");
						}
					}
					System.out.println();
				}
				long time = System.currentTimeMillis() - start;
				System.out.println("Time consumed: " + time + "; gap=" + master.getMIPRelativeGap());
				builder.append("Time: " + time + "\t gap=" + master.getMIPRelativeGap() + "\t value="
						+ master.getObjValue() + "\r\n");

				if (this.writeToFile) {
					try {
						Writer writer = new BufferedWriter(new FileWriter(Lang.projectRoot()
								+ "\\data\\flexibilityDesign\\result\\result.txt", true));
						writer.write(builder.toString());
						writer.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}

			master.end();
			return YValue;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * y_{ij}=1 means product i can be produced by plant j
	 * **/
	private double[][] setFlexibilityConfig() {
		double[][] y = new double[n][m];
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				y[i][j] = 1.0;
		return y;
	}

}
